// $Id: main.cpp 56 2007-05-31 20:18:18Z DarkAn9el $
/***************************************************************************
 *   Copyright (C) 2007 by Andrea Blankenstijn, Ulrich Giraud              *
 *   <darkan9el@gmail.com>, <blade.124@laposte.net>                        *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

using namespace std; /*! \namespace std il s'agit du seul espace de nom utilisé donc utilisation par defaut*/

#include "main.h"
#include "conversions.h"
#ifdef AbcQtUi
  #include <QApplication>
  #include "qt/qtui.h"
#endif
#ifdef AbcGtkUi
  #include <gtkmm.h>
  #include "gtk/gtkui.h"
#endif


int main (int argc, char **argv) {
  table["pi"] = M_PI;
  table["e"] = M_E;
  table["c"] = 299792458;
  table["g"] = 9.8;
  table["obase"] = 10;  // table pour la base de sortie
  table["ibase"] = 10;  // table pour la base d'entrée
  table["precision"] = 15; // précision par défaut de la sortie de cout
  table["mode"] = 0; // affichage flottant par défaut

  #ifdef AbcQtUi
    QApplication app(argc, argv);
    QtUi gui;
    gui.show();
    app.exec();
  #else
  #ifdef AbcGtkUi
    Gtk::Main kit(argc, argv);
    GtkUi gtkui;
    Gtk::Main::run(gtkui);
  #else
    DefaultUi();
  #endif
  #endif

  return no_of_errors;
}

/**
 * Cette fonction crée une simili interface en mode console
 * permettant d'utiliser la calculatrice sans avoir de librairie
 * comme ncurses, gtk ou qt
 */
void DefaultUi(void) {
  Conversions bases;
  motd ();
  while (cin) {
    get_token();
    SetDisplayMode((unsigned short) table["mode"]);
    cout.precision((int) table["precision"]);
    if (curr_tok == END) break;
    if (curr_tok == PRINT) continue;
    if (table["obase"] == 10) cout << expr(false) << '\n';
    else if (table["obase"] <= 32 && table["obase"] >= 2)
      cout << bases.DecToX(expr(false),(unsigned short)table["obase"]) << endl;
    else {
      error("obase invalide!, retour a la base 10");
      table["obase"] = 10;
    }
  }
}

/**
 * Fonction de gestion des erreurs
 * \return le nombre 1 pour ne pas arreter le calcul
 */
double error(const string& s) {
  no_of_errors++;
  cerr << "erreur: " << s << '\n';
  return 1;
}

/**
 * Exécute des fonctions mathématiques sur des nombres
 * telles que cos, sin, racine etc.
 * \return double nombre traité
 * \param [in] to_execute la fonction math à exécuter sur le nombre
 */
double ExecFunctions(string to_execute) {
  double number_value = 0;

  get_token();
  number_value = expr(true); // on calcule déjà entre les parenthèses
  if (curr_tok != RP) return error(") attendue");
  get_token();

  if (to_execute == "sqrt") return sqrt(number_value);
  if (to_execute == "cos") return cos(number_value);
  if (to_execute == "sin") return sin(number_value);
  if (to_execute == "tan") return tan(number_value);
  if (to_execute == "acos") return acos(number_value);
  if (to_execute == "asin") return asin(number_value);
  if (to_execute == "atan") return atan(number_value);
  if (to_execute == "ln") return log(number_value);
  if (to_execute == "log") return log10(number_value);

  return error("Erreur d'exécution de la fonction");
}

/**
 * S'occuppe des additions et soustractions (operations de plus
 * petite priorité)
 * \return le nombre additionné avec le reste du calcul
 * \param[in] get recherche ou non du prochain opérateur
 */
double expr(bool get) {
  double left = term(get); // on appelle les calculs de priorité plus élévée

  while (true)
    switch (curr_tok) {
      case PLUS:
        left += term(true);
        break;
      case MINUS:
        left -= term(true);
        break;
      default:
        return left;
    }
}

/**
 * Extrait un nombre de cin, et le passe a XToDec qui le convertis
 * en base 10 dans le but de calculer
 * \return Nombre en base 10
 */
double extract_number() {
  Conversions bases;
  char ch, number[150]={0}; // et oui encore une chaine C...
  int i=0;

  do {
    cin.get(ch);
    number[i] = ch;
    i++;
  } while (isdigit(ch) || isupper(ch) || ch == '.');
  cin.putback(ch); // car on a extrait un caractère en trop
  number[i-1] = '\0';// comme d'hab on formate bien la chaîne
  if (table["obase"] <= 32 && table["obase"] >= 2)
    return bases.XToDec(number, (unsigned int)table["ibase"]);
  else {
    error("ibase invalide! retour a la base 10");
    table["obase"] = 10;
    return 1;
  }
}

/**
 * Teste si la variable str corresponds à une constante définie
 * comme par exemple pi
 * \return bool true si c'est une constante, false sinon
 * \param [in] str La chaîne de caracteres à comparer aux constantes
 */
bool IsConst(string str) {
  if (str == "pi" || str == "e" || str == "c" || str == "g" ||
      str == "G" )
    return true;
  return false;
}

/**
 * Fonction de test des mots clés, afin de pouvoir réagir
 * en conséquence et surtout ne pas affecter une valeur à
 * un mot clé
 * \return bool true si on a un mot clé, false si c'est une variable
 * \param [in] str La chaine à tester
 */
bool IsKeyword(string str) {
  if (str == "cos" || str == "sin" || str == "tan" ||
      str == "acos" || str == "asin" || str == "atan" ||
      str == "sqrt" || str == "ln" || str == "log")
    return true;
  else
    return false;
}

/**
 * Fonction d'affichage d'une bannière d'acceuil
 * \return void
 */
void motd() {
  cout << "Another bc rev. " << SVN_REVSTR " " << "(" << SVN_REVDATE << ")" << endl;
  cout << "pour changer de base de calcul, utilisez les variables" << endl;
  cout << "obase et ibase (par défaut, 10) attention A-Z ne sont pas des variables !" << endl;
  cout << "Pour une utilisation plus aisée, entrez d'abord la obase ;)" << endl;
  cout << "entrez un calcul (quit pour sortir) :" << endl;
}

/**
 * S'occuppe du reste du calcul, les parenthèses, variables etc.
 * \return nombre extrait du cin/nombre traité
 * \param[in] get recherche ou non du prochain opérateur
 */
double prim(bool get) {
  if (get) get_token();

  switch (curr_tok) {
    case KEY:
    {
      double v = ExecFunctions(string_value);
      return v;
    }
    case NUMBER:
    { double v = number_value;
    get_token();
    return v;
    }
    case NAME:
    { double& v = table[string_value];
    if (get_token() == ASSIGN && !IsConst(string_value)) v = expr(true);
    return v;
    }
    case MINUS:
      return -prim(true);
    case LP:
    { double e = expr(true);
    if (curr_tok != RP) return error(")attendu");
    get_token();
    return e;
    }
    default:
      return error("primary attendu");
  }
}

double ReadTable(string table_to_read) {
  return table[table_to_read];
}

/**
 * Permet de choisir le format de sortie des nombres en base 10,
 * soit scientific, ou fixed pour affichage flottant.
 * \return void
 * \param [in] mode 1 pour fixed, 2 pour scientific.
 */
void SetDisplayMode(unsigned short mode)
{
  switch(mode)
  {
    case 0:
      cout.setf(ios_base::adjustfield);
      break;
    case 1:
      cout.setf(ios_base::fixed, ios_base::floatfield);
      break;
    case 2:
      cout.setf(ios_base::scientific, ios_base::floatfield);
      break;
    default:
      error("mode d'affichage non supporté.");
      break;
  }
}

/**
 * S'occuppe des multiplications, divisions et puissances (operations
 * de priorité moyenne)
 * \return le nombre additionné avec le reste du calcul
 * \param[in] get recherche ou non du prochain opérateur
 */
double term(bool get) {
  double left = prim(get); // On regarde tout ce qui n'est pas calcul et qui donc a une priorité plus elevée

  while (true)
    switch (curr_tok) {
      case EXP: //priorité mal gérée...
        left = pow(left, prim(true));
        break;
      case MUL:
        left *= prim(true);
        break;
      case DIV:
        left /= prim(true);
        break;
      default:
        return left;
    }
}

/**
 * Permets d'obtenir le type de données sur lesquelles on travaille
 * est appelée par les fonctions de calcul
 * \return Le type de données
 */
Token_value get_token() {
  char ch;

  do { // permets de sauter les espaces blancs à l'exception de '\n'
    if(!cin.get(ch)) return curr_tok = END;
  } while (ch != '\n' && isspace(ch));

  if(((ch >= '0' && ch <= '9') || ch == '.') && ((int)table["ibase"])==10) {
    cin.putback(ch);
    cin >> number_value;
    return curr_tok=NUMBER;
  }
  if (((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')
       || ch == '.') && (int)table["ibase"] != 10) {
    cin.putback(ch);
    number_value=extract_number(); // extrait le nombre et le convertis en /10
    return curr_tok=NUMBER;
  }

  else {
    switch (ch) {
      case ';':
      case '\n':
        return curr_tok=PRINT;
      case '*':case '/':case '+':case '-':case '(':case ')':case '=':case'^':
        return curr_tok=Token_value(ch);
      default: // on vérifie que l'on ai pas une variable sinon on retourne un PRINT pour abandonner ce calcul
        if (isalpha(ch)) {
          string_value = ch;
          while (cin.get(ch) && isalnum(ch)) string_value.push_back(ch);
          cin.putback(ch);
          if (IsKeyword(string_value)) return curr_tok=KEY;
          if (string_value == "quit") return curr_tok=END;
          return curr_tok=NAME;
      }
      error("mot clé erronné");
      return curr_tok=PRINT;
    }
  }
}
